/*
 * Copyright (c) 2002, 2003 David Corbin and others. All rights reserved. This
 * program and the accompanying materials are made available under the terms of
 * the Common Public License v0.5 which accompanies this distribution, and is
 * available at http://www.eclipse.org/legal/cpl-v05.html
 *
 * Contributors: Boris Pruessmann - Initial implementation.
 */
package net.sourceforge.perforce.core.internal.api;

import com.google.common.base.Preconditions;

import net.sourceforge.perforce.api.IClientApi;
import net.sourceforge.perforce.api.IError;
import net.sourceforge.perforce.api.IPerforceCommand;
import net.sourceforge.perforce.api.PerforceApiPlugin;
import net.sourceforge.perforce.core.IClientUserListener;
import net.sourceforge.perforce.core.PerforceCorePlugin;
import net.sourceforge.perforce.core.PerforceException;
import net.sourceforge.perforce.core.api.IPerforceServer;
import net.sourceforge.perforce.core.internal.Policy;

import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;

import java.io.File;

/**
 * @version $Revision: 491 $
 * @author <a href="mailto:bpruessmann@users.sourceforge.net">Boris
 *         Pruessmann</a>
 */
public class Connection {
  // ---------------------------------------------------------------------------
  // Private Variables

  /** Stores the reference to the Perforce Client API. */
  private IClientApi clientApi;
  /** Stores the Perforce Server to which this connection belongs. */
  private final IPerforceServer server;
  /** Current working directory */
  private File cwd;
  /** Stores the timestamp of last use. */
  private long lastUse;
  /** Indicates that the connection is valid. */
  private boolean valid;

  // -------------------------------------------------------------------------
  // Public Constructors

  /**
   * Constructor for Connection.
   *
   * @param server the server to connect.
   */
  public Connection(IPerforceServer server) {
    this.server = Preconditions.checkNotNull(server);
  }

  //----------------------------------------------------------------------------
  // -- Public Methods

  /**
   * Checks whether the connection is open or not.
   *
   * @return true if connection is open.
   */
  public boolean isOpen() {
    return (clientApi != null && !clientApi.dropped());
  }

  /**
   * Opens the connection to the Perforce Server.
   *
   * @param monitor for progress feedback.
   * @throws PerforceException in case of an error.
   */
  public void open(IProgressMonitor monitor) throws PerforceException {
    if (isOpen() && isValid()) {
      return;
    } else {
      if (clientApi != null) {
        close();
      }

      monitor.beginTask("Opening connection to " + server.getPort(), 100); //$NON-NLS-1$
      try {
        clientApi = PerforceApiPlugin.createClientApi();
        clientApi.setPort(server.getPort());
        clientApi.setClient(server.getClient());
        clientApi.setUser(server.getUser());
        clientApi.setPassword(server.getPassword());

        clientApi.setProtocol("specstring", ""); //$NON-NLS-1$ //$NON-NLS-2$
        clientApi.setProtocol("tag", ""); //$NON-NLS-1$ //$NON-NLS-2$

        monitor.worked(30);

        clientApi.init(server.getCharset());

        monitor.worked(70);

        IError error = clientApi.getPerforceError();
        if (error.isError()) {
          IStatus status =
              new Status(IStatus.ERROR, PerforceCorePlugin.ID, error.getGeneric(), error
                  .format(IError.EF_PLAIN), null);

          throw new PerforceException(status);
        }
      } finally {
        monitor.done();
      }
    }
  }

  /**
   * Call this method to run the given IPerforceCommand with the specified
   * arguments.
   *
   * @param arguments the arguments.
   * @param command the command to run.
   * @param monitor the progress monitor.
   */
  synchronized void run(String[] arguments, IPerforceCommand command, IProgressMonitor monitor) {
    if (clientApi == null) {
      return;
    }

    monitor = Policy.monitorFor(monitor);
    monitor.beginTask("p4 " + command.getCommand() + ": ", //$NON-NLS-1$ //$NON-NLS-2$
        IProgressMonitor.UNKNOWN);
    try {
      synchronized (clientApi) {
        final IClientUserListener[] listeners =
            PerforceCorePlugin.getPlugin().getClientUserListeners();
        for (int i = 0; i < listeners.length; i++) {
          listeners[i].run(command, arguments);
        }

        if (cwd != null) {
          clientApi.setCwd(cwd.getAbsolutePath());
        }
        clientApi.run(arguments, new ClientUserListenerDelegate(command, listeners));
      }
    } finally {
      monitor.done();
      lastUse = System.currentTimeMillis();
    }
  }

  /** Call this method to close the connection to the Perforce Server. */
  public void close() {
    if (clientApi != null) {
      clientApi.dispose();
      clientApi = null;
    }
  }

  /**
   * Returns the timestamp of the last use.
   *
   * @return last use timestamp.
   */
  public synchronized long getLastUse() {
    return lastUse;
  }

  /** Called to mark this connection invalid. */
  public void invalidate() {
    valid = false;
  }


  //----------------------------------------------------------------------------
  // - Package Private Methods

  void setClientRoot(IPath clientRoot) {
    if (clientRoot == null) {
      cwd = null;
    } else {
      // The working directory needs to be in a directory under the client root
      // so just pick the 1st directory that you find.
      File[] files = clientRoot.toFile().listFiles();
      for (File file : files) {
        if (file.isDirectory()) {
          cwd = file;
          return;
        }
      }
    }
  }

  //----------------------------------------------------------------------------
  // - Private Methods

  /**
   * Checks if the connection is still valid. A connection may become invalid
   * e.g. if any of the connection parameters of the server change.
   *
   * @return <code>true</code> or <code>false</code>.
   */
  private boolean isValid() {
    return valid;
  }

}
